﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Carbon.Web.Forms.Controls
{
    [ParseChildren(true), PersistChildren(false), ViewStateModeById]
    public class CollectionEditor : ViewControlBase, INamingContainer
    {
        #region Fields (7)
        private ICollection m_Collection = default(ICollection);

        private bool m_Collection_calculated = false;

        private List<CollectionEditorItem> m_Items = new List<CollectionEditorItem>();

        private ITemplate m_FooterTemplate = default(ITemplate);
        private ITemplate m_HeaderTemplate = default(ITemplate);
        private ITemplate m_ItemTemplate = default(ITemplate);
        private ITemplate m_SeparatorTemplate = default(ITemplate);
        #endregion

        #region Properties (6)
        public ICollection Collection
        {
            get
            {
                if (!m_Collection_calculated)
                {
                    m_Collection = GetCollection(ModelValue);
                    m_Collection_calculated = true;
                }

                return m_Collection;
            }
        }
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(CollectionEditorItem))]
        public ITemplate FooterTemplate
        {
            get
            {
                return m_FooterTemplate;
            }
            set
            {
                m_FooterTemplate = value;
            }
        }
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(CollectionEditorItem))]
        public ITemplate HeaderTemplate
        {
            get
            {
                return m_HeaderTemplate;
            }
            set
            {
                m_HeaderTemplate = value;
            }
        }
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(CollectionEditorItem))]
        public ITemplate ItemTemplate
        {
            get
            {
                return m_ItemTemplate;
            }
            set
            {
                m_ItemTemplate = value;
            }
        }
        public List<CollectionEditorItem> Items
        {
            get
            {
                return m_Items;
            }
        }
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(RepeaterItem))]
        public ITemplate SeparatorTemplate
        {
            get
            {
                return m_SeparatorTemplate;
            }
            set
            {
                m_SeparatorTemplate = value;
            }
        }
        #endregion

        #region Methods (9)

        public CollectionEditor()
        {
        }

        protected override IEditor CreateEditor()
        {
            throw new InvalidOperationException();
        }

        private int[] ItemIds
        {
            get
            {
                Object cachedItemIdsObject = ViewState["ItemIds"];

                if (cachedItemIdsObject == null)
                {
                    return null;
                }

                return (int[])cachedItemIdsObject;
            }
            set
            {
                this.TrackViewState();
                ViewState["ItemIds"] = value;
            }
        }

        private void AutogenerateNextId(ICollection<int> currentIds, ref int lastAutogeneratedId)
        {
            for (int i = lastAutogeneratedId + 1; ; ++i)
            {
                if (!currentIds.Contains(i))
                {
                    lastAutogeneratedId = i;

                    return;
                }
            }
        }

        protected override void CreateViewControls()
        {
            if (this.CreatedViewControls)
            {
                CollectionEditorItem header = null;
                CollectionEditorItem footer = null;
                Dictionary<Object, CollectionEditorItem> itemsByObject = new Dictionary<object, CollectionEditorItem>();

                foreach (CollectionEditorItem item in this.Items)
                {
                    if (item.ItemType == ListItemType.Header)
                    {
                        header = item;
                    }
                    else if (item.ItemType == ListItemType.Footer)
                    {
                        footer = item;
                    }
                    else if (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)
                    {
                        itemsByObject.Add(item.DataItem, item);
                    }
                }

                Items.Clear();

                if (header != null)
                {
                    Items.Add(header);
                }

                HashSet<int> usedIds = new HashSet<int>(ItemIds);
                HashSet<CollectionEditorItem> usedItems = new HashSet<CollectionEditorItem>();
                int lastGeneratedId = -1;

                int i = 0;
                Object[] collection = Collection.Cast<Object>().ToArray(); // so we iterate it only once because could be expensive to iterate
                int count = collection.Length;

                // reuse existing and add new if necessary
                foreach (Object o in collection)
                {
                    CollectionEditorItem child = null;
                    
                    if (itemsByObject.ContainsKey(o))
                    {
                        child = itemsByObject[o];
                        child.DataItemIndex = i;
                        child.ItemType = i % 2 == 0 ? ListItemType.Item : ListItemType.AlternatingItem;
                    }
                    else 
                    {
                        child = new CollectionEditorItem(i, i % 2 == 0 ? ListItemType.Item : ListItemType.AlternatingItem, o);
                        ItemTemplate.InstantiateIn(child);
                        AutogenerateNextId(usedIds, ref lastGeneratedId);
                        child.ID = ConvertInvariant.ToString(lastGeneratedId);
                        itemsByObject[o] = child;
                        this.Controls.Add(child);
                    }

                    usedItems.Add(child);
                    Items.Add(child);
    
                    i++;
                }

                // remove non used items
                foreach (CollectionEditorItem item in itemsByObject.Values)
                {
                    if (!usedItems.Contains(item))
                        this.Controls.Remove(item);
                }

                int[] ids = new int[collection.Length];
                for (i = 0; i < count; ++i)
                {
                    ids[i] = ConvertInvariant.ToInt32(itemsByObject[collection[i]].ID);
                }

                this.ItemIds = ids;
    
                if (footer != null)
                {
                    Items.Add(footer);
                    Controls.Add(footer);
                }
            }
            else
            {
                this.Controls.Clear();
                this.Items.Clear();

                // header
                {
                    CollectionEditorItem child;

                    if (HeaderTemplate != null)
                    {
                        child = new CollectionEditorItem(-1, ListItemType.Header);
                        HeaderTemplate.InstantiateIn(child);
                        child.ID = "h";
                        this.Controls.Add(child);
                        Items.Add(child);
                    }
                }

                // items
                {
                    int i = 0;
                    HashSet<Object> objects = new HashSet<object>();

                    Object[] collection = Collection.Cast<Object>().ToArray();

                    int[] ids = ItemIds;

                    int count = collection.Length;
                    if (ids == null)
                    {
                        ids = new int[count];
                        for (i = 0; i < count; ++i)
                        {
                            ids[i] = i;
                        }

                        this.ItemIds = ids;
                    }
                    else
                    {
                         if ((ids.Length == count) == false)  throw new InvalidOperationException("(ids.Length == count) == false");
                    }

                    i = 0;
                    foreach (Object o in collection)
                    {
                        CollectionEditorItem child = null;

                        child = new CollectionEditorItem(i, i % 2 == 0 ? ListItemType.Item : ListItemType.AlternatingItem, o);
                        ItemTemplate.InstantiateIn(child);
                        child.ID = ConvertInvariant.ToString(ids[i]);
                        this.Controls.Add(child);
                        Items.Add(child);

                        if (objects.Contains(o))
                            throw new InvalidOperationException("Objects must be unique");

                        objects.Add(o);

                        i++;
                    }
                }

                // footer
                {
                    CollectionEditorItem child;

                    if (FooterTemplate != null)
                    {
                        child = new CollectionEditorItem(-1, ListItemType.Footer);
                        FooterTemplate.InstantiateIn(child);
                        child.ID = "f";
                        this.Controls.Add(child);
                        Items.Add(child);
                    }
                }

                CreatedViewControls = true;
            }
        }

        protected override object SaveViewState()
        {
            return base.SaveViewState();
        }

        private ICollection GetCollection(object value)
        {
            if (value == null) throw new NullReferenceException("value");
            ICollection collection = null;
            if (value is ICollection)
            {
                collection = (ICollection)value;
            }
            else if (value is IList)
            {
                collection = (ICollection)value;
            }
            else if (value is IEnumerable)
            {
                collection = ((IEnumerable)value).Cast<Object>().ToList().AsReadOnly();
            }
            else
            {
                throw new InvalidOperationException(string.Format("Invalid data source for path '{0}'", this.DataPath));
            }

            return collection;
        }

        protected virtual void OnRemove(CollectionEditorRemoveEventArgs args)
        {
            if (Collection is IList)
            {
                ((IList)Collection).Remove(args.Item);
            }
            else if (Collection is ICollection)
            {
                throw new ArgumentOutOfRangeException("Collection");
            }
        }

        protected override bool OnBubbleEvent(object source, System.EventArgs args)
        {
            if (args is CollectionEditorCommandEventArgs)
            {
                CollectionEditorCommandEventArgs commandArgs = (CollectionEditorCommandEventArgs)args;

                if (commandArgs.CommandName == "Remove")
                {
                    OnRemove(new CollectionEditorRemoveEventArgs(commandArgs.Item.DataItem));

                    this.RaiseBubbleEvent(this, new UpdateModelEventArgs());
                }
                else
                    return base.OnBubbleEvent(source, args);

                return true;
            }
            else
                return base.OnBubbleEvent(source, args);
        }

        public override void OnUpdateControl(object sender, UpdateControlEventArgs args)
        {
            base.OnUpdateControl(sender, args);

            m_Collection_calculated = false;
        }

        public override void OnUpdateModel(object sender, UpdateModelEventArgs args)
        {
            if (!this.Binder.IsReadOnly)
                this.ModelValue = this.Collection;
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
        }

        protected override void OnDataBinding(EventArgs e)
        {
            base.OnDataBinding(e);
        }

        protected override void RenderChildren(HtmlTextWriter writer)
        {
            CollectionEditorItem header = this.Items.Where(i => i.ItemType == ListItemType.Header).SingleOrDefault();
            CollectionEditorItem footer = this.Items.Where(i => i.ItemType == ListItemType.Footer).SingleOrDefault();

            IEnumerable<CollectionEditorItem> items = this.Items.Where(i => i.ItemType == ListItemType.Item || i.ItemType == ListItemType.AlternatingItem);

            if (header != null)
                header.RenderControl(writer);

            foreach (CollectionEditorItem item in items)
            {
                item.RenderControl(writer);
            }

            if (footer != null)
                footer.RenderControl(writer);
        }
        #endregion
    }
}
